#ifndef __REDCELL_HPP__
#define __REDCELL_HPP__

#include <cassert>
#include "data.hpp"
#include "marshal.hpp"

namespace redcell {

  // dump

#define INDENT "   "

  inline void dumpTableLevel(std::ostream &os, const Table &tab, const size_t lv) {
    String prefix;
    for (size_t i=0; i<lv; ++i) {
      prefix += INDENT;
    }

    //os << "RefCnt: " << tab.cnt << " ";

    os << "{\n";
    IntKey expkey = 0;
    for (TableImpl::const_iterator i=tab->begin(); i!=tab->end(); ++i) {
      const Key &key = i->first;

      os << prefix << INDENT << key;

#if REDCELL_STRINGKEYS
      switch (key.type()) {
      case INTKEY:
	os << (key.ik() == expkey ? "  " : ". ");
	expkey = key.ik() + 1;
	break;
      case STRKEY:
	os << "  ";
	break;
      default:
	assert(false);
      }
#else
      os << (key == expkey ? "  " : ". ");
#endif


      const Variant &var = i->second;

      switch (var.type()) {
      case NIL: os << "NIL"; break;
      case INTEGER: os << "INT " << var.integer; break;
      case REAL: os << "REAL " << var.real; break;
      case STRING: os << "STR " << var.string(); break;
      case TABLE: dumpTableLevel(os, var.table(), lv + 1); break;
      }
      os << "\n";
    }
    os << prefix << "}";
  }


  inline String dump(const Table &tab) {
    std::ostringstream os;
    dumpTableLevel(os, tab, 0);
    return os.str();
  }


  //-------------------------
  // serialization


  template <typename OBuffer>
  void writeTable(OBuffer &buf, const Table &tab);


  template <typename OBuffer>
  void writeItem(OBuffer &buf, const Variant &var) {
    switch (var.type()) {
    case NIL: 
      buf.putNil(); 
      break;
    case INTEGER:
      buf.putInteger(var.integer);
      break;
    case REAL:
      buf.putReal(var.real);
      break;
    case STRING:
      buf.putString(var.string());
      break;
    case TABLE:
      if (var.table()->empty()) {
	buf.putEmptySub();
      } else {
	buf.putSub();
        writeTable(buf, var.table());
      }
      break;
    default:
      assert(false);
    }
  }


  template <typename OBuffer>
  void writeTable(OBuffer &buf, const Table &tab) {
    IntKey expkey = 0;
    for (TableImpl::const_iterator i=tab->begin(); i!=tab->end(); ++i) {
      switch (i->first.type()) {
      case INTKEY: {
	IntKey ik = i->first.ik();
	size_t keyinc = ik - expkey;
	if (keyinc > 0) {
	  buf.putKeySkip(keyinc);
	}
	expkey = ik + 1;
	break;
      }
      case STRKEY:
	buf.putStrKey(i->first.sk());
	break;
      default:
	assert(false);
      }
      writeItem(buf, i->second);
    }
    buf.putEnd();
  }

  /*
  template <typename Iterator>
  void write(const Table &tab, Iterator begin, Iterator end) {
    OutputBuffer<Iterator> buf(begin);
    writeTable(buf, tab);
  }
  */

  template <typename Iterator>
  void iteratorPack(const Table &tab, Iterator begin) {
    OutputBuffer<Iterator> buf(begin);
    writeTable(buf, tab);
  }


  inline void streamPack(const Table &tab, std::ostream &os) {
    std::ostreambuf_iterator<char> begin(os);
    iteratorPack(tab, begin);
  }


  inline String pack(const Table &tab) {
    std::ostringstream os;
    streamPack(tab, os);
    return os.str();
  }



  //---------------------------------------------------
  // deserialization

  //*

  template <typename IBuffer>
  void readItem(IBuffer &buf, Table &tab, const char flag, const Key key) {
    switch (flag) {
    case F_NIL:
      tab.nil(key);
      break;
    case F_FALSE:
      tab.i(key) = FALSE;
      break;
    case F_TRUE:
      tab.i(key) = TRUE;
      break;
    case F_POS:
      tab.i(key) = buf.getInteger(0);
      break;
    case F_NEG:
      tab.i(key) = buf.getInteger(-1);
      break;
    case F_FLOAT:
      tab.r(key) = buf.getFloat();
      break;
    case F_DOUBLE: 
      tab.r(key) = buf.getDouble();
      break;
    case F_STR:
      tab.s(key) = buf.getString();
      break;
    case F_EMPTYSUB:
      tab.t(key);
      break;
    case F_SUB:
      readTable(buf, tab.t(key));
      break;
    default:
      throw MarshalError("Bad stream content");
    }
  }


  template <typename IBuffer>
  void readTable(IBuffer &buf, Table &tab) {
    IntKey ikey = 0;

#if REDCELL_STRINGKEYS
    StrKey skey;
    char ktype = INTKEY;
#endif

    while (true) {
      char flag = buf.getFlag();
      switch (flag) {
      case F_KEYSKIP:

#if REDCELL_STRINGKEYS
	ktype = INTKEY;
#endif
	ikey += buf.getKeySkip();
	break;

#if REDCELL_STRINGKEYS
      case F_STRKEY:
	ktype = STRKEY;
	skey = buf.getStrKey();
	break;
#endif

      case F_END:
	return;
      default:

#if REDCELL_STRINGKEYS
	switch (ktype) {
	case INTKEY: readItem(buf, tab, flag, ikey++); break;
	case STRKEY: readItem(buf, tab, flag, skey); break;
	default: assert(false);
	}
#else
	readItem(buf, tab, flag, ikey++);
#endif
	break;
      }
    }
  }


  template <typename Iterator>
  Table iteratorUnpack(Iterator begin, Iterator end) {
    InputBuffer<Iterator> buf(begin, end);
    Table tab;
    readTable(buf, tab);
    return tab;
  }

  inline Table streamUnpack(std::istream &is) {
    std::istreambuf_iterator<char> begin(is);
    std::istreambuf_iterator<char> end;
    return iteratorUnpack(begin, end);
  }

  inline Table unpack(const String str) {
    std::istringstream is(str);
    return streamUnpack(is);
  }

  //*/
}

#endif
